package edu.ncsu.csc.csc454.project.gui.wheel_panel;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;

import edu.ncsu.csc.csc454.project.ApplicationWheel;
import edu.ncsu.csc.csc454.project.gui.testing.TestPanel;
import edu.ncsu.csc.csc454.project.resources.Constants;
import edu.ncsu.csc.csc454.project.wheel.NodeChain;
import edu.ncsu.csc.csc454.project.wheel.node.Node;

/**
 * Represents a JPanel that that will be used to render the current ApplicationWheel.
 * @author Team AW
 * @version 0.1
 */
@SuppressWarnings("serial")
public class WheelDisplay extends JPanel {

    private static final boolean DEBUG      = false;

    private boolean              showAbout  = false;
    private boolean              canDrag;
    private boolean              dragging;

    private int                  xMid;
    private int                  yMid;
    private int                  dropPosition;

    private double               radius;

    private String               title      = "Application Wheel";
    private String               name0      = "CSC 454 - HCI";
    private String               name1      = "Chris Hutcherson";
    private String               name2      = "Michael Teal";
    private String               name3      = "Scott Vaughn";
    private String               name4      = "Kenny Yarboro";

    private String               close      = "- [ TOUCH TO CLOSE ] -";

    private Point                mouseOrigin;
    private Point                mouseCurrent;
    private Point                iconOffset = new Point();

    private Node                 activeNode;
    private Node                 clickedNode;
    private NodeChain            chain;

    private JLabel               appName    = new JLabel();
    private JLabel               tapText    = new JLabel();

    private TrashCan             trashCan;

    private Shape                clip;
    private Listener             listener   = new Listener();
    private Graphics2D           g;
    private FontMetrics          metrics;
    private Dimension            frameSize;
    private BufferedImage        bgImage;
    private Iterator<Node>       iterator;

    private TestPanel            testPanel  = new TestPanel();

    public WheelDisplay() {
        setLayout(null);
        chain = ApplicationWheel.getChain();
        createBackground();
        addIcons();
        initListeners();
        initAppName();
        initTapText();
    }

    private void createBackground() {
        try {
            bgImage = ImageIO.read(new File(Constants.BGIMAGE_PATH));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void addIcons() {
        iterator = chain.getIterator();
        while (iterator.hasNext()) {
            add(iterator.next());
        }
    }

    private void initListeners() {
        addMouseListener(listener);
        addMouseMotionListener(listener);
    }

    private void initAppName() {
        appName.setForeground(new Color(0xFFffff));
        appName.setFont(new Font("Arial", Font.BOLD, 14));
        add(appName);
    }
    
    private void initTapText() {
    	tapText.setForeground(new Color(0xFFffff));
    	tapText.setFont(new Font("Arial", Font.PLAIN, 11));
        add(tapText);
    }

    public void initFrame() {
        frameSize = getSize();
        xMid = frameSize.width / 2;
        yMid = frameSize.height / 2;
        radius = Math.min(frameSize.height, frameSize.width) * 0.3;
        initTrashCan();
        if (DEBUG) initTestPanel();
    }

    private void initTrashCan() {
        trashCan = new TrashCan();
        Rectangle bounds = trashCan.getBounds();
        bounds.x = xMid - trashCan.getPreferredSize().width / 2;
        bounds.y = yMid - trashCan.getPreferredSize().height / 2;
        bounds.width = trashCan.getPreferredSize().width;
        bounds.height = trashCan.getPreferredSize().height;
        trashCan.setBounds(bounds);
        add(trashCan);
    }

    private void initTestPanel() {
        testPanel.setBounds(0, 0, frameSize.width, frameSize.height);
        add(testPanel, 0);
    }

    /**
     * Draws the current wheel state on the canvas.
     * @param gX the paint object
     */
    @Override
    public void paintComponent(Graphics gX) {
        super.paintComponent(gX);
        g = (Graphics2D) gX;
        chain = ApplicationWheel.getChain();
        render();
    }

    private void render() {
        g.drawImage(bgImage, null, 0, 0);

        int i = 0;
        double skip = 2 * Math.PI;
        skip /= dropPosition == -1 && dragging ? chain.size() - 1 : chain.size();
        Node node;
        Rectangle bounds;

        if (DEBUG) testPanel.refresh(xMid, yMid, radius, skip, dropPosition);

        iterator = chain.getIterator();

        while (iterator.hasNext()) {
            node = iterator.next();
            bounds = node.getBounds();

            if (dragging && i == dropPosition) i++;

            if (dragging && node == activeNode) {
                bounds.x = mouseCurrent.x - iconOffset.x;
                bounds.y = mouseCurrent.y - iconOffset.y;
            } else {
                bounds.x = (int) (radius * Math.cos(skip * i) + xMid - node.getWidth() / 2);
                bounds.y = (int) (radius * Math.sin(skip * i) + yMid - node.getHeight() / 2);
                i++;
            }
            node.setBounds(bounds);
        }

        if (showAbout) renderAboutDialog();
    }

	/**
     * Draws the about graphic onto the canvas.
     */
    private void renderAboutDialog() {
        int height = getHeight() - 8;

        clip = new RoundRectangle2D.Double(xMid - 90, 4, 180, height, 120, 60);
        g.setColor(Color.white);
        g.fill(clip);
        g.setColor(Color.black);
        g.draw(clip);

        Font oldFont = g.getFont();
        g.setFont(new Font("Arial Black", Font.PLAIN, 11));
        metrics = g.getFontMetrics();

        g.drawString(title, xMid - metrics.stringWidth(title) / 2, 8 + metrics.getAscent());

        g.setFont(oldFont);
        metrics = g.getFontMetrics();

        int ascent = metrics.getAscent();

        g.drawString(name0, xMid - metrics.stringWidth(name0) / 2, yMid - 2 * ascent - 8);
        g.drawString(name1, xMid - metrics.stringWidth(name1) / 2, yMid - ascent - 4);
        g.drawString(name2, xMid - metrics.stringWidth(name2) / 2, yMid);
        g.drawString(name3, xMid - metrics.stringWidth(name3) / 2, yMid + ascent + 4);
        g.drawString(name4, xMid - metrics.stringWidth(name4) / 2, yMid + 2 * ascent + 8);

        g.setColor(Color.red);
        g.drawString(close, xMid - metrics.stringWidth(close) / 2, height - ascent + 4);
    }

    /**
     * Paints the about graphic.
     */
    public void showAboutDialog() {
        showAbout = true;
        displayComponents(false);
        repaint();
    }

    /**
     * Destroys the about graphic.
     */
    public void killAboutDialog() {
        showAbout = false;
        clip = null;
        displayComponents(true);
        repaint();
    }

    private void displayComponents(boolean display) {
        for (Component c : getComponents()) {
            c.setVisible(display);
        }
        trashCan.setVisible(false);
    }

    private void setCaptionText(String text) {
    	if( text == null || text.contains("Opening") || text.contains("Yummy"))
    		tapText.setText(null);
    	else
    	{
    		tapText.setText("Tap again to open.");
            Dimension d2 = tapText.getPreferredSize();
            tapText.setBounds(xMid - d2.width / 2, (yMid - d2.height / 2) +14, d2.width, d2.height);
    	}    	
    	if (text == null) {
            appName.setText(null);
            
        } else {
            appName.setText(text);
            Dimension d = appName.getPreferredSize();
            appName.setBounds(xMid - d.width / 2, (yMid - d.height / 2) - 5, d.width, d.height);
        }        
    }

    /**
     * Testing function.
     * @param msg
     */
    private void trace(String msg) {
        if (DEBUG) {
            System.out.println(msg);
        }
    }

    // -------------------------------------------------------------------------------------------//
    // ---------------------------------Private Inner Classes-------------------------------------//
    // -------------------------------------------------------------------------------------------//

    /**
     * Defines a mouse input listener for the about graphic.
     */
    private class Listener extends MouseInputAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            if (clip != null) {
                trace("Removing About Box.");
                killAboutDialog();
                repaint();
                return;
            }

            if (DEBUG) remove(testPanel);
            Component c = getComponentAt(e.getPoint());
            if (DEBUG) add(testPanel, 0);

            if (!(c instanceof Node)) {
                trace("Pressed a non-node object.");
                if (activeNode != null) {
                    trace("Stopping active node.");
                    setCaptionText(null);
                    activeNode.stopAnimation();
                    activeNode = null;
                }
                clickedNode = null;
                return;
            }

            clickedNode = (Node) c;
            trace("Pressed a node: " + clickedNode.getIconInfo().getCaption());

            if (clickedNode == activeNode) {
                trace("Clicked node is the currently active node.");
                activeNode.pauseAnimation();
                setComponentZOrder(activeNode, DEBUG ? 1 : 0);
                mouseOrigin = e.getPoint();
                iconOffset.x = mouseOrigin.x - activeNode.getLocation().x;
                iconOffset.y = mouseOrigin.y - activeNode.getLocation().y;
                setCaptionText(null);
                canDrag = true;
            } else {
                trace("Clicked node is not currently active.");

                if (activeNode != null) {
                    trace("Some other node is active.");
                    activeNode.stopAnimation();
                }

                trace("Activating the clicked node.");
                activeNode = clickedNode;
                activeNode.startAnimation();
                setCaptionText(activeNode.getIconInfo().getCaption());
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            trace("Mouse released.");

            if (dragging) {
                if (dropPosition == -1) {
                    activeNode.stopAnimation();
                    chain.remove(activeNode);
                    remove(activeNode);
                    activeNode = null;
                    setCaptionText("Yummy");
                } else {
                    chain.move(dropPosition, activeNode);
                    activeNode.resumeAnimation();
                    setCaptionText(activeNode.getIconInfo().getCaption());
                }
            } else {
                if (activeNode != null && activeNode.isPaused()) {
                    trace("Opening application: " + activeNode.getIconInfo().getCaption());
                    activeNode.stopAnimation();
                    setCaptionText("Opening...");
                    activeNode = null;
                }
            }

            canDrag = dragging = false;
            trashCan.setVisible(false);
            repaint();
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (!canDrag) return;

            mouseCurrent = e.getPoint();

            if (!dragging) {
                dragging = mouseOrigin.distance(mouseCurrent) < 4 ? false : true;
                if (!dragging) return;
                trashCan.setVisible(true);
            }

            setDropPosition();
            repaint();
        }

        private void setDropPosition() {
            int x = mouseCurrent.x - xMid;
            int y = mouseCurrent.y - yMid;

            double hyp = Math.sqrt(x * x + y * y);
            if (hyp < radius * 0.5) {
                dropPosition = -1;
                return;
            }

            double theta = Math.atan2(y, x);
            if (theta < 0) theta += (2 * Math.PI);
            dropPosition = (int) Math.round(theta * chain.size() / (2 * Math.PI)) % chain.size();
            trace("(x, y) = (" + x + ", " + y + "); theta = " + theta + "; drop = " + dropPosition);
        }
    }
}
